#include "GnSystemPCH.h"
#include "GnEngineBegingManager.h"
#include "GnSystemEBM.h"
#include "GnLog.h"


guint8 GnEngineBegingManager::ms_uiNumLibraries = 0;
guint8 GnEngineBegingManager::ms_uiNumInitializedLibraries = 0;
bool GnEngineBegingManager::ms_bInitialized = false;
GnEngineBegingManager::LibraryNode GnEngineBegingManager::ms_akLibraries[NI_NUM_LIBRARIES];
guint8 GnEngineBegingManager::ms_auiProcessOrder[NI_NUM_LIBRARIES];

bool GnEngineBegingManager::ms_bAutoCreatedInitOptions = false;

GnEngineBegingManager::GnEngineBegingManager(void)
{
}

GnEngineBegingManager::~GnEngineBegingManager(void)
{
}


//---------------------------------------------------------------------------
void GnEngineBegingManager::Startup()
{
	// GnSystem is a required library, so its Init function is called
	// directly instead of being registered.
	GnSystemEBM::Startup();

	ms_bInitialized = true;
	ProcessAccumulatedLibraries();

	GnLog("GnEngineBegingManager Startup\n");
}

void GnEngineBegingManager::Shutdown()
{
	// GnSystem is a required library, so its Shutdown functions is called
	// directly instead of being registered.    

	// Shutdown in reverse order of initialization
	for (int i = ms_uiNumInitializedLibraries-1; i >= 0 ; i--)
	{
		(*ms_akLibraries[ms_auiProcessOrder[i]].m_pfnShutdownFunction)();
	}
	ms_uiNumInitializedLibraries = 0;

	GnSystemEBM::Shutdown();

	ms_bInitialized = false;

	GnLog("GnEngineBegingManager Shutdown\n");
}

void GnEngineBegingManager::AddLibrary(const gchar* libName, const gchar* dependencie,
	ShutdownFunction funcStartup, ShutdownFunction funcShutdowns)
{
	// You hit this assert if there are more libraries registering than the
	// amount of allocated space. To change the amount of allocated space,
	// modify NI_NUM_LIBRARIES in GnStaticManager.h.
	GnAssert(ms_uiNumLibraries < NI_NUM_LIBRARIES); 

	// Store all there is to know about the library
	ms_akLibraries[ms_uiNumLibraries].m_pcName = libName;
	ms_akLibraries[ms_uiNumLibraries].m_uiIndex = ms_uiNumLibraries;
	ms_akLibraries[ms_uiNumLibraries].m_pcDependsOn = dependencie;
	ms_akLibraries[ms_uiNumLibraries].m_uiNumDependencies = 0;
	ms_akLibraries[ms_uiNumLibraries].m_uiMaxDependencies = 0;
	ms_akLibraries[ms_uiNumLibraries].m_puiDependencies = 0;
	ms_akLibraries[ms_uiNumLibraries].m_pfnInitFunction = funcStartup;
	ms_akLibraries[ms_uiNumLibraries].m_pfnShutdownFunction = funcShutdowns;

	// Initially the process order is the order it is added.
	ms_auiProcessOrder[ms_uiNumLibraries] = ms_uiNumLibraries;

	ms_uiNumLibraries++; 
}

void GnEngineBegingManager::RemoveLibrary(const gchar* pcLibName)
{
	guint8 uiRemove = 0;
	for (uiRemove = 0; uiRemove < ms_uiNumLibraries; uiRemove++)
	{
		if (!strcmp(pcLibName, ms_akLibraries[uiRemove].m_pcName))
		{
			break;
		}
	}

	// You hit this assert if you try to remove a library that has not been
	// added.
	GnAssert(uiRemove != ms_uiNumLibraries);

	// You hit this assert if the library you are trying to remove still has
	// active dependents. Remove the dependents first.
	GnAssert(!HasDependents(uiRemove));

	// This test is only valid because we do not re-arrange the order
	// of initialization unless we also update ms_uiNumInitializedLibraries.
	if (uiRemove < ms_uiNumInitializedLibraries)
	{
		ms_akLibraries[uiRemove].m_pfnShutdownFunction();
		ms_uiNumInitializedLibraries--;
	}

	for (guint8 ui = uiRemove + 1; ui < ms_uiNumLibraries; ui++)
	{
		--ms_akLibraries[ui].m_uiIndex;
		ms_akLibraries[ui - 1] = ms_akLibraries[ui];
	}

	// Find it in the process order list, and modify the process order
	// list indices to reflect the shuffle done above.
	guint8 uiOrderRemove = ms_uiNumLibraries;
	for (guint8 ui = 0; ui < ms_uiNumLibraries; ui++)
	{
		if (ms_auiProcessOrder[ui] == uiRemove)
		{
			uiOrderRemove = ui;
		}
		else if (ms_auiProcessOrder[ui] > uiRemove)
		{
			ms_auiProcessOrder[ui]--;
		}
	}

	// You hit this assert if the library is not in the process order.
	// Failure indicates an internal problem in this class.
	GnAssert(uiOrderRemove != ms_uiNumLibraries);

	for (guint8 ui = uiOrderRemove + 1; ui < ms_uiNumLibraries; ui++)
	{
		ms_auiProcessOrder[ui - 1] = ms_auiProcessOrder[ui];
	}

	ms_uiNumLibraries--;
}
//---------------------------------------------------------------------------
bool GnEngineBegingManager::HasDependents(const guint8 uiLibIndex)
{
	const gchar* pcName = ms_akLibraries[uiLibIndex].m_pcName;
	for (guint8 ui = 0; ui < ms_uiNumInitializedLibraries; ui++)
	{
		if (uiLibIndex == ui)
			continue;

		if (strstr(ms_akLibraries[ui].m_pcDependsOn, pcName))
			return true;
	}

	return false;
}

void GnEngineBegingManager::ProcessAccumulatedLibraries()
{
	if (!ms_bInitialized || !ms_uiNumLibraries)
		return;

	if (ms_uiNumInitializedLibraries)
	{
		for (guint8 ui = ms_uiNumInitializedLibraries; ui < ms_uiNumLibraries; ui++)
		{
			GnAssert(DependenciesInitialized(ui));

			(*ms_akLibraries[ui].m_pfnInitFunction)();
		}        

		ms_uiNumInitializedLibraries = ms_uiNumLibraries;
	}
	else
	{
		GnVerify(ComputeProcessOrder());

		for (guint8 ui = 0; ui < ms_uiNumLibraries; ui++)
		{
			(*ms_akLibraries[ms_auiProcessOrder[ui]].m_pfnInitFunction)();
		}        

		ms_uiNumInitializedLibraries = ms_uiNumLibraries;
	}
}

bool GnEngineBegingManager::DependenciesInitialized(const guint8 uiLibIndex)
{
	gsize uiLength = strlen(ms_akLibraries[uiLibIndex].m_pcDependsOn) + 1;
	char* pcDependsOn = (char*)GnExternalMalloc(uiLength);
	GnStrncpy(pcDependsOn, uiLength, ms_akLibraries[uiLibIndex].m_pcDependsOn,
		uiLength - 1);
	char* pcContext = 0;
	char* pcDependency = 0;
	pcDependency = GnStrtok(pcDependsOn, " ", &pcContext);
	while (pcDependency)
	{
		guint8 uiDepIndex = 0;
		for ( ; uiDepIndex < ms_uiNumInitializedLibraries ; uiDepIndex++)
		{
			if (!strcmp(pcDependency, ms_akLibraries[uiDepIndex].m_pcName))
			{
				break;
			}
		}
		if (uiDepIndex == ms_uiNumInitializedLibraries)
			return false;
		pcDependency = GnStrtok(0, " ", &pcContext);
	}

	GnExternalFree(pcDependsOn);

	return true;
}

bool GnEngineBegingManager::ComputeProcessOrder()
{
	if (ms_uiNumLibraries == 0)
		return true;

	guint8 auiUnvisited[NI_NUM_LIBRARIES];
	guint8 uiNumUnvisited = 0;
	guint8 auiDependencyFree[NI_NUM_LIBRARIES];
	guint8 uiNumDependencyFree = 0;

	// Set all the edges in the graph
	for (guint8 ui = 0; ui < ms_uiNumLibraries; ui++)
	{
		if (!ConstructDependencyGraph(ui, auiUnvisited, uiNumUnvisited,
			auiDependencyFree, uiNumDependencyFree))
		{
			return false;
		}
	}

	GnAssert(uiNumDependencyFree > 0);

	guint8 uiNumProcessed = 0;
	while (uiNumDependencyFree > 0)
	{
		guint8 uiNextLib = auiDependencyFree[uiNumDependencyFree - 1];
		--uiNumDependencyFree;

		ms_auiProcessOrder[uiNumProcessed] = uiNextLib;

		RemoveDependencies(uiNextLib, auiUnvisited, uiNumUnvisited,
			auiDependencyFree, uiNumDependencyFree);

		++uiNumProcessed;
	}

	GnAssert(uiNumUnvisited == 0);
	GnAssert(uiNumProcessed == ms_uiNumLibraries);

	return true;
}

bool GnEngineBegingManager::ConstructDependencyGraph(const guint8 uiLibIndex, guint8* auiUnvisited,
	guint8& uiNumUnvisited, guint8* auiDependencyFree, guint8& uiNumDependencyFree)
{
	gsize uiLength = strlen(ms_akLibraries[uiLibIndex].m_pcDependsOn) + 1;
	char* pcDependsOn = (char*)GnExternalMalloc(uiLength);
	GnStrncpy(pcDependsOn, uiLength, ms_akLibraries[uiLibIndex].m_pcDependsOn,
		uiLength - 1);
	char* pcContext = 0;
	char* pcDependency = 0;
	pcDependency = GnStrtok(pcDependsOn, " ", &pcContext);

	if (!pcDependency)
	{
		auiDependencyFree[uiNumDependencyFree] = uiLibIndex;
		++uiNumDependencyFree;
	}
	else
	{
		while (pcDependency)
		{
			guint8 uiDepIndex = 0;
			for ( ; uiDepIndex < ms_uiNumLibraries ; uiDepIndex++)
			{
				if (!strcmp(pcDependency, ms_akLibraries[uiDepIndex].m_pcName))
				{
					AddDependency(uiLibIndex, uiDepIndex);
					break;
				}
			}
			if (uiDepIndex == ms_uiNumLibraries)
			{
				GnLog("------- ERROR -------\n");
				//char acMsg[1024];
				//GnSprintf(acMsg, 1024, "GnStaticDataManager: Can't find "
				//	"library %s that library %s depends on.\n",
				//	pcDependency, ms_akLibraries[uiLibIndex].m_pcName);
				//GnOutputDebugString(acMsg);
				//GnSprintf(acMsg, 1024, "GnStaticDataManager: Did you include "
				//	"the top-level header file for %s?\n", pcDependency);
				//GnOutputDebugString(acMsg);

				GnAssert(!"Missing #include library. See debugger output text.");
				return false;
			}
			pcDependency = GnStrtok(0, " ", &pcContext);
		}

		auiUnvisited[uiNumUnvisited] = uiLibIndex;
		++uiNumUnvisited;
	}

	GnExternalFree(pcDependsOn);

	return true;
}

void GnEngineBegingManager::AddDependency(const guint8 uiLibIndex, const guint8 uiDependentIndex)
{
	LibraryNode* pkLib = ms_akLibraries + uiLibIndex;
	if (pkLib->m_uiMaxDependencies == pkLib->m_uiNumDependencies)
	{
		pkLib->m_uiMaxDependencies += 5;
		guint8* puiNewDependencies = (guint8*)GnExternalMalloc(pkLib->m_uiMaxDependencies);
		for (guint8 ui = 0; ui < pkLib->m_uiNumDependencies; ui++)
		{
			puiNewDependencies[ui] = pkLib->m_puiDependencies[ui];
		}
		if (pkLib->m_puiDependencies)
			GnExternalFree(pkLib->m_puiDependencies);

		pkLib->m_puiDependencies = puiNewDependencies;
	}

	pkLib->m_puiDependencies[pkLib->m_uiNumDependencies] = uiDependentIndex;
	++pkLib->m_uiNumDependencies;
}

void GnEngineBegingManager::RemoveDependencies(const guint8 uiLibIndex, guint8* auiUnvisited,
	guint8& uiNumUnvisited, guint8* auiDependencyFree, guint8& uiNumDependencyFree)
{
	guint8 ui = 0;
	while (ui < uiNumUnvisited)
	{
		LibraryNode* pkLib = ms_akLibraries + auiUnvisited[ui];
		bool bRemoved = false;

		for (guint8 uj = 0 ; uj < (guint8)pkLib->m_uiNumDependencies; uj++)
		{
			if (pkLib->m_puiDependencies[uj] == uiLibIndex)
			{
				for (guint8 uk = uj + 1; uk < pkLib->m_uiNumDependencies; uk++)
				{
					pkLib->m_puiDependencies[uk - 1] = pkLib->m_puiDependencies[uk];
				}
				--pkLib->m_uiNumDependencies;
				if (!pkLib->m_uiNumDependencies)
				{
					GnExternalFree(pkLib->m_puiDependencies);
					pkLib->m_puiDependencies = 0;
					pkLib->m_uiMaxDependencies = 0;

					auiDependencyFree[uiNumDependencyFree] = auiUnvisited[ui];
					++uiNumDependencyFree;

					for (guint8 uk = ui + 1; uk < uiNumUnvisited; uk++)
					{
						auiUnvisited[uk - 1] = auiUnvisited[uk];
					}
					--uiNumUnvisited;
					bRemoved = true;
				}
				break;
			}
		}

		if (!bRemoved)
		{
			ui++;
		}
	}
}